using System;

//
// * Copyright (c) 2002-2009 "Neo Technology,"
// *     Network Engine for Objects in Lund AB [http://neotechnology.com]
// *
// * This file is part of Neo4j.
// * 
// * Neo4j is free software: you can redistribute it and/or modify
// * it under the terms of the GNU Affero General Public License as
// * published by the Free Software Foundation, either version 3 of the
// * License, or (at your option) any later version.
// * 
// * This program is distributed in the hope that it will be useful,
// * but WITHOUT ANY WARRANTY; without even the implied warranty of
// * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// * GNU Affero General Public License for more details.
// * 
// * You should have received a copy of the GNU Affero General Public License
// * along with this program. If not, see <http://www.gnu.org/licenses/>.
// 
namespace org.neo4j.kernel.impl.nioneo.store
{


	using ReadOnlyNeoException = org.neo4j.kernel.impl.core.ReadOnlyNeoException;

///
/// <summary> * Contains common implementation for <seealso cref="AbstractStore"/> and
/// * <seealso cref="AbstractDynamicStore"/>. </summary>
/// 
	public abstract class CommonAbstractStore
	{
		protected internal static readonly Logger logger = Logger.getLogger(typeof(CommonAbstractStore).Name);

///    
///     <summary> * Returns the type and version that identifies this store.
///     *  </summary>
///     * <returns> This store's implementation type and version identifier </returns>
///     
		public abstract string getTypeAndVersionDescriptor();

///    
///     <summary> * Called from the constructor after the end header has been checked. The
///     * store implementation can setup it's
///     * <seealso cref="PersistenceWindow persistence windows"/> and other resources that
///     * are needed by overriding this implementation.
///     * <p>
///     * This default implementation does nothing.
///     *  </summary>
///     * <exception cref="IOException">
///     *             If unable to initialize </exception>
///     
		protected internal virtual void initStorage()
		{
		}

		protected internal virtual bool versionFound(string version)
		{
			return true;
		}

///    
///     <summary> * This method should close/release all resources that the implementation of
///     * this store has allocated and is called just before the <CODE>close()</CODE>
///     * method returns. Override this method to clean up stuff created in
///     * <seealso cref="#initStorage()"/> method.
///     * <p>
///     * This default implementation does nothing.
///     *  </summary>
///     * <exception cref="IOException">
///     *             If unable to close </exception>
///     
		protected internal virtual void closeStorage()
		{
		}

///    
///     <summary> * Should do first validation on store validating stuff like version and id
///     * generator. This method is called by constructors.
///     *  </summary>
///     * <exception cref="IOException">
///     *             If unable to load store </exception>
///     
		protected internal abstract void loadStorage();

///    
///     <summary> * Should rebuild the id generator from scratch.
///     *  </summary>
///     * <exception cref="IOException">
///     *             If unable to rebuild id generator. </exception>
///     
		protected internal abstract void rebuildIdGenerator();

	// default node store id generator grab size
		protected internal const int DEFAULT_ID_GRAB_SIZE = 1024;

		private readonly string storageFileName;
		private IdGenerator idGenerator = null;
		private FileChannel fileChannel = null;
		private PersistenceWindowPool windowPool;
		private bool storeOk = true;
		private FileLock fileLock;
		private bool grabFileLock = true;

//JAVA TO VB & C# CONVERTER TODO TASK: Java wildcard generics are not converted to .NET:
//ORIGINAL LINE: private Map<?,?> config = null;
		private Map<?, ?> config = null;

		private bool readOnly = false;

///    
///     <summary> * Opens and validates the store contained in <CODE>fileName</CODE>
///     * loading any configuration defined in <CODE>config</CODE>. After
///     * validation the <CODE>initStorage</CODE> method is called.
///     * <p>
///     * If the store had a clean shutdown it will be marked as <CODE>ok</CODE>
///     * and the <seealso cref="#validate()"/> method will not throw exception when invoked.
///     * If a problem was found when opening the store the <seealso cref="#makeStoreOk()"/>
///     * must be invoked else <seealso cref="#validate()"/> will throw exception.
///     * 
///     * throws IOException if the unable to open the storage or if the 
///     * <CODE>initStorage</CODE> method fails
///     *  </summary>
///     * <param name="fileName">
///     *            The name of the store </param>
///     * <param name="config">
///     *            The configuration for store (may be null) </param>
///     * <exception cref="IOException">
///     *             If store doesn't exist </exception>
///     
		public CommonAbstractStore<T1>(string fileName, Map<T1> config)
		{
			this.storageFileName = fileName;
			this.config = config;
			if (config != null)
			{
				string fileLock = (string) config.get("grab_file_lock");
				if (fileLock != null && fileLock.ToLower().Equals("false"))
				{
					grabFileLock = false;
				}
			}
			checkStorage();
			loadStorage();
			initStorage();
		}

		internal virtual bool isReadOnly()
		{
			return readOnly;
		}

///    
///     <summary> * Opens and validates the store contained in <CODE>fileName</CODE>.
///     * After validation the <CODE>initStorage</CODE> method is called.
///     * <p>
///     * If the store had a clean shutdown it will be marked as <CODE>ok</CODE>
///     * and the <seealso cref="#validate()"/> method will not throw exception when invoked.
///     * If a problem was found when opening the store the <seealso cref="#makeStoreOk()"/>
///     * must be invoked else <seealso cref="#validate()"/> will throw exception.
///     * 
///     * throws IOException if the unable to open the storage or if the 
///     * <CODE>initStorage</CODE> method fails
///     *  </summary>
///     * <param name="fileName">
///     *            The name of the store </param>
///     * <exception cref="IOException">
///     *             If store doesn't exist </exception>
///     
		public CommonAbstractStore(string fileName)
		{
			this.storageFileName = fileName;
			checkStorage();
			loadStorage();
			initStorage();
		}

		private void checkStorage()
		{
			if (config != null)
			{
				bool isReadOnly = (bool) config.get("read_only");
				if (isReadOnly != null)
				{
					readOnly = isReadOnly;
				}
			}
			if (!new File(storageFileName).exists())
			{
				throw new IllegalStateException("No such store[" + storageFileName + "]");
			}
			try
			{
				if (!readOnly)
				{
					this.fileChannel = new RandomAccessFile(storageFileName, "rw").getChannel();
				}
				else
				{
					this.fileChannel = new RandomAccessFile(storageFileName, "r").getChannel();
				}
			}
			catch (IOException e)
			{
				throw new UnderlyingStorageException("Unable to open file " + storageFileName, e);
			}
			try
			{
				if (!readOnly && grabFileLock)
				{
					this.fileLock = this.fileChannel.tryLock();
					if (fileLock == null)
					{
						fileChannel.Close();
						throw new IllegalStateException("Unable to lock store [" + storageFileName + "], this is usually a result of some " + "other Neo4j kernel running using the same store.");
					}
				}
			}
			catch (IOException e)
			{
				throw new UnderlyingStorageException("Unable to lock store[" + storageFileName + "]");
			}
		}

///    
///     <summary> * Marks this store as "not ok".
///     *  </summary>
///     
		protected internal virtual void setStoreNotOk()
		{
			if (readOnly)
			{
				throw new UnderlyingStorageException("Cannot start up on non clean store as read only");
			}
			storeOk = false;
		}

///    
///     <summary> * If store is "not ok" <CODE>false</CODE> is returned.
///     *  </summary>
///     * <returns> True if this store is ok </returns>
///     
		protected internal virtual bool getStoreOk()
		{
			return storeOk;
		}

///    
///     <summary> * Sets the <seealso cref="PersistenceWindowPool"/> for this store to use. Normally
///     * this is set in the <seealso cref="#loadStorage()"/> method. This method must be
///     * invoked with a valid "pool" before any of the
///     * <seealso cref="#acquireWindow(int, OperationType)"/>
///     * <seealso cref="#releaseWindow(PersistenceWindow)"/> <seealso cref="#flush(int)"/>
///     * <seealso cref="#forget(int)"/> <seealso cref="#close()"/> methods are invoked.
///     *  </summary>
///     * <param name="pool">
///     *            The window pool this store should use </param>
///     
		protected internal virtual void setWindowPool(PersistenceWindowPool pool)
		{
			this.windowPool = pool;
		}

///    
///     <summary> * Returns the next id for this store's <seealso cref="IdGenerator"/>.
///     *  </summary>
///     * <returns> The next free id </returns>
///     * <exception cref="IOException">
///     *             If unable to get next free id </exception>
///     
		public virtual int nextId()
		{
			return (int) idGenerator.nextId();
		}

///    
///     <summary> * Frees an id for this store's <seealso cref="IdGenerator"/>.
///     *  </summary>
///     * <param name="id">
///     *            The id to free </param>
///     * <exception cref="IOException">
///     *             If unable to free the id </exception>
///     
		public virtual void freeId(int id)
		{
			idGenerator.freeId(makeUnsignedInt(id));
		}

		private long makeUnsignedInt(int signedInteger)
		{
			return signedInteger & 0xFFFFFFFFL;
		}

///    
///     <summary> * Return the highest id in use.
///     *  </summary>
///     * <returns> The highest id in use. </returns>
///     
		protected internal virtual long getHighId()
		{
			return idGenerator.getHighId();
		}

///    
///     <summary> * Sets the highest id in use (use this when rebuilding id generator).
///     *  </summary>
///     * <param name="highId">
///     *            The high id to set. </param>
///     
		protected internal virtual void setHighId(long highId)
		{
			if (idGenerator != null)
			{
				idGenerator.setHighId(highId);
			}
		}

		protected internal virtual bool getIfMemoryMapped()
		{
			if (getConfig() != null)
			{
				string useMemMapped = (string) getConfig().get("use_memory_mapped_buffers");
				if (useMemMapped != null && useMemMapped.ToLower().Equals("false"))
				{
					return false;
				}
			}
			return true;
		}

///    
///     <summary> * Returns memory assigned for
///     * <seealso cref="MappedPersistenceWindow memory mapped windows"/> in bytes. The
///     * configuration map passed in one constructor is checked for an entry with
///     * this stores name.
///     *  </summary>
///     * <returns> The number of bytes memory mapped windows this store has </returns>
///     
		protected internal virtual long getMappedMem()
		{
			if (getConfig() != null)
			{
				string convertSlash = storageFileName.Replace('\\', '/');
				string realName = convertSlash.Substring(convertSlash.LastIndexOf('/') + 1);
				string mem = (string) getConfig().get(realName + ".mapped_memory");
				if (mem != null)
				{
					long multiplier = 1;
					if (mem.EndsWith("M"))
					{
						multiplier = 1024 * 1024;
						mem = mem.Substring(0, mem.Length - 1);
					}
					else if (mem.EndsWith("k"))
					{
						multiplier = 1024;
						mem = mem.Substring(0, mem.Length - 1);
					}
					else if (mem.EndsWith("G"))
					{
						multiplier = 1024*1024*1024;
						mem = mem.Substring(0, mem.Length - 1);
					}
					try
					{
						return Convert.ToInt32(mem) * multiplier;
					}
					catch (NumberFormatException e)
					{
						logger.info("Unable to parse mapped memory[" + mem + "] string for " + storageFileName);
					}
				}
			}
			return 0;
		}

///    
///     <summary> * If store is not ok a call to this method will rebuild the {@link 
///     * IdGenerator} used by this store and if successful mark it as 
///     * <CODE>ok</CODE>.
///     *  </summary>
///     * <exception cref="IOException">
///     *             If unable to rebuild id generator </exception>
///     
		public virtual void makeStoreOk()
		{
			if (!storeOk)
			{
				if (readOnly)
				{
					throw new ReadOnlyNeoException();
				}
				rebuildIdGenerator();
				storeOk = true;
			}
		}

		public virtual void rebuildIdGenerators()
		{
			if (readOnly)
			{
				throw new ReadOnlyNeoException();
			}
			rebuildIdGenerator();
		}

///    
///     <summary> * Returns the configuration map if set in constructor.
///     *  </summary>
///     * <returns> A map containing configuration or <CODE>null<CODE> if no
///     *         configuration map set. </returns>
///     
//JAVA TO VB & C# CONVERTER TODO TASK: Java wildcard generics are not converted to .NET:
//ORIGINAL LINE: public Map<?,?> getConfig()
		public virtual Map<?, ?> getConfig()
		{
			return config;
		}

///    
///     <summary> * Acquires a <seealso cref="PersistenceWindow"/> for <CODE>position</CODE> and
///     * operation <CODE>type</CODE>. Window must be released after operation
///     * has been performed via <seealso cref="#releaseWindow(PersistenceWindow)"/>.
///     *  </summary>
///     * <param name="position">
///     *            The record position </param>
///     * <param name="type">
///     *            The operation type </param>
///     * <returns> a persistence window encapsulating the record </returns>
///     * <exception cref="IOException">
///     *             If unable to acquire window </exception>
///     
		protected internal virtual PersistenceWindow acquireWindow(int sPosition, OperationType type)
		{
			long position = makeUnsignedInt(sPosition);
			if (!isInRecoveryMode() && (position > idGenerator.getHighId() || !storeOk))
			{
				throw new InvalidRecordException("Position[" + position + "] requested for operation is high id[" + idGenerator.getHighId() + "] or store is flagged as dirty[" + storeOk + "]");
			}
			return windowPool.acquire(position, type);
		}

///    
///     <summary> * Releases the window and writes the data (async) if the 
///     * <CODE>window</CODE> was a <seealso cref="PersistenceRow"/>.
///     *  </summary>
///     * <param name="window">
///     *            The window to be released </param>
///     * <exception cref="IOException">
///     *             If window was a <CODE>DirectPersistenceRow</CODE> and
///     *             unable to write out its data to the store </exception>
///     
		protected internal virtual void releaseWindow(PersistenceWindow window)
		{
			windowPool.release(window);
		}

		public virtual void flushAll()
		{
			windowPool.flushAll();
		}

		private bool isRecovered = false;

		protected internal virtual bool isInRecoveryMode()
		{
			return isRecovered;
		}

		protected internal virtual void setRecovered()
		{
			isRecovered = true;
		}

		protected internal virtual void unsetRecovered()
		{
			isRecovered = false;
		}

///    
///     <summary> * Returns the name of this store.
///     *  </summary>
///     * <returns> The name of this store </returns>
///     
		public virtual string getStorageFileName()
		{
			return storageFileName;
		}

///    
///     <summary> * Opens the <seealso cref="IdGenerator"/> used by this store.
///     *  </summary>
///     * <exception cref="IOException">
///     *             If unable to open the id generator </exception>
///     
		protected internal virtual void openIdGenerator()
		{
			idGenerator = new IdGeneratorImpl(storageFileName + ".id", DEFAULT_ID_GRAB_SIZE);
		}

		protected internal virtual void openReadOnlyIdGenerator(int recordSize)
		{
			try
			{
				idGenerator = new ReadOnlyIdGenerator(storageFileName + ".id", fileChannel.size() / recordSize);
			}
			catch (IOException e)
			{
				throw new UnderlyingStorageException(e);
			}
		}

///    
///     <summary> * Closed the <seealso cref="IdGenerator"/> used by this store
///     *  </summary>
///     * <exception cref="IOException">
///     *             If unable to close this store </exception>
///     
		protected internal virtual void closeIdGenerator()
		{
			if (idGenerator != null)
			{
				idGenerator.Close();
			}
		}

///    
///     <summary> * Closes this store. This will cause all buffers and channels to be closed.
///     * Requesting an operation from after this method has been invoked is
///     * illegal and an exception will be thrown.
///     * <p>
///     * This method will start by invoking the <seealso cref="#closeStorage"/> method
///     * giving the implementing store way to do anything that it needs to do
///     * before the fileChannel is closed.
///     *  </summary>
///     * <exception cref="IOException">
///     *             If problem when invoking <seealso cref="#closeStorage()"/> </exception>
///     
		public virtual void close()
		{
			if (fileChannel == null)
			{
				return;
			}
			closeStorage();
			if (windowPool != null)
			{
				windowPool.Close();
				windowPool = null;
			}
			if (isReadOnly())
			{
				try
				{
					fileChannel.Close();
				}
				catch (IOException e)
				{
					throw new UnderlyingStorageException(e);
				}
				return;
			}
			long highId = idGenerator.getHighId();
			int recordSize = -1;
			if (this is AbstractDynamicStore)
			{
				recordSize = ((AbstractDynamicStore) this).getBlockSize();
			}
			else if (this is AbstractStore)
			{
				recordSize = ((AbstractStore) this).getRecordSize();
			}
			closeIdGenerator();
			bool success = false;
			IOException storedIoe = null;
		// hack for WINBLOWS
			if (!readOnly)
			{
				for (int i = 0; i < 10; i++)
				{
					try
					{
						fileChannel.position(highId * recordSize);
						ByteBuffer buffer = ByteBuffer.wrap(getTypeAndVersionDescriptor().getBytes());
						fileChannel.write(buffer);
						fileChannel.truncate(fileChannel.position());
						fileChannel.force(false);
						fileLock.release();
						fileChannel.Close();
						fileChannel = null;
						success = true;
						break;
					}
					catch (IOException e)
					{
						storedIoe = e;
						System.gc();
					}
				}
			}
			else
			{
				try
				{
					fileChannel.Close();
				}
				catch (IOException e)
				{
					e.printStackTrace();
				}
			}
			if (!success)
			{
				throw new UnderlyingStorageException("Unable to close store " + getStorageFileName(), storedIoe);
			}
		}

///    
///     <summary> * Returns a <CODE>FileChannel</CODE> to this storage's file. If 
///     * <CODE>close()</CODE> method has been invoked <CODE>null</CODE> will be 
///     * returned.
///     *  </summary>
///     * <returns> A file channel to this storage </returns>
///     
		protected internal FileChannel getFileChannel()
		{
			return fileChannel;
		}

///    
///     * <returns> The highest possible id in use, -1 if no id in use. </returns>
///     
		public virtual long getHighestPossibleIdInUse()
		{
			return idGenerator.getHighId() - 1;
		}

///    
///     * <returns> The total number of ids in use. </returns>
///     
		public virtual long getNumberOfIdsInUse()
		{
			return idGenerator.getNumberOfIdsInUse();
		}
	}
}